package com.martini.satoken01.anno;

import com.martini.satoken01.exception.AccessLimitExceedException;
import com.martini.satoken01.model.AjaxJson;
import com.martini.satoken01.service.LimitInfoCacheService;
import com.martini.satoken01.service.UserAuthService;
import com.martini.satoken01.utils.RequestContext;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;

/**
 * @author martini at 2021/12/17 19:27
 */
@Slf4j
@Aspect
@Component
public class AccessLimitedAspect {

    private final LimitInfoCacheService limitInfoCacheService;

    private final UserAuthService userAuthService;

    public AccessLimitedAspect(LimitInfoCacheService limitInfoCacheService, UserAuthService userAuthService) {
        this.limitInfoCacheService = limitInfoCacheService;
        this.userAuthService = userAuthService;
    }

    @Pointcut("@annotation(AccessLimited)")
    public void pointcut() { }

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint pjp) {
        try {
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            AccessLimited accessLimited = signature.getMethod().getAnnotation(AccessLimited.class);
            boolean afterLogin = accessLimited.limitAfterLogin();
            log.debug("access limited needs login: {}", accessLimited.limitAfterLogin());
            int times = accessLimited.limitTimes();
            int nowTimes = 0;
            long remainMilliseconds = 0;
            if (afterLogin) {
                boolean isLogin = userAuthService.isLogin();
                // 缓存的key，因为是方法访问限制，因此格式为: [用户主体]:[全类名]#[方法名]
                // 无需主机唯一标志符, 因为即使分布式也是同一个接口
                // 主体id可能是账户id，可能是ip
                Object subjectId = null;
                switch (accessLimited.limitType()) {
                    case ACCOUNT:
                        subjectId = userAuthService.getSubjectId();
                        break;
                    case IP:
                        subjectId = RequestContext.getHttpRequest().getRemoteAddr();
                        break;
                    default:
                        log.info("不受支持的限制主体类型");
                }
                if (subjectId == null) {
                    throw new RuntimeException("用户主体信息为空");
                }
                String typeName = pjp.getTarget().getClass().getTypeName();
                log.debug("type name: {}", typeName);
                String methodName = signature.getMethod().getName();
                log.debug("method name: {}", methodName);
                String key = subjectId + ":" + typeName + "#" + methodName;
                log.debug("cache key: {}", key);
                // 从缓存中获取key对应的value
    //            TypeReference<List<Long>> listTypeReference = new TypeReference<List<Long>>(){};
                List value = limitInfoCacheService.get(key, List.class);
                log.debug("value value: {}", value);
                long limitDuration = accessLimited.limitDuration();
                if (value != null) {
                    Iterator iterator = value.iterator();
                    long currentTimeMillis = System.currentTimeMillis();
                    while (iterator.hasNext()) {
                        long milliseconds = (long) iterator.next();
                        if (currentTimeMillis - milliseconds > limitDuration) {
                            System.out.println("有，remove");
                            iterator.remove();
                        }
                    }
                }
                if (value != null && !value.isEmpty()) {
                    // 保存过
                    log.debug("value class: {}", value.getClass().getName());
                    if (times <= 0) {
                        throw new RuntimeException("限制次数必须大于0");
                    }
                    long farthestAccessMilliseconds = (long) value.get(0);
                    long needWaitTime = limitDuration - (System.currentTimeMillis() - farthestAccessMilliseconds);
                    nowTimes = value.size();
                    if (nowTimes < times) {
                        value.add(System.currentTimeMillis());
                        this.limitInfoCacheService.put(key, value);
                        if (nowTimes + 1 == times) {
                            remainMilliseconds = needWaitTime > 0 ? needWaitTime : 0;
                        }
                    } else {
                        if (needWaitTime < 0) {
                            value.remove(0);
                            value.add(System.currentTimeMillis());
                        } else {
                            throw new AccessLimitExceedException("一分钟内只能搜索" + times + "次，您已超过限制");
                        }
                    }
                } else {
                    // 没有保存过
                    List<Long> timestampList = new ArrayList<>();
                    timestampList.add(System.currentTimeMillis());
                    this.limitInfoCacheService.put(key, timestampList);
                }
            }
            log.info("cache service info: {}", this.limitInfoCacheService.info());
            // region 参数注入
            String timesAttr = accessLimited.limitInjectRemainTimesAttr();
            String permissionTimeAttr = accessLimited.limitInjectNextPermissionTimeAttr();
            boolean needInject = !StringUtils.isEmpty(timesAttr) || !StringUtils.isEmpty(permissionTimeAttr);
            Object res = null;
            if (needInject) {
                Object[] args = pjp.getArgs();
                String[] parameterNames = ((MethodSignature) pjp.getSignature()).getParameterNames();
                if (!StringUtils.isEmpty(timesAttr)) {
                    int targetIndex = findTargetAttrIndex(parameterNames, timesAttr);
                    args[targetIndex] = times - nowTimes - 1;
                }
                if (!StringUtils.isEmpty(permissionTimeAttr)) {
                    int targetIndex = findTargetAttrIndex(parameterNames, permissionTimeAttr);
                    args[targetIndex] = remainMilliseconds;
                }
                res = pjp.proceed(args);
            }
            else {
                res = pjp.proceed();
            }
            return res;
            // endregion
        } catch (AccessLimitExceedException e) {
            e.printStackTrace();
            System.out.println(e.getMessage());
            return AjaxJson.fail().setMsg(e.getMessage());
        } catch (Throwable e) {
            e.printStackTrace();
            return AjaxJson.fail().setMsg(e.getMessage());
        }
    }

    private int findTargetAttrIndex(String[] parameterNames, String targetAttr) {
        int targetIndex = -1;
        for (int i = 0; i < parameterNames.length; i++) {
            if (Objects.equals(targetAttr, parameterNames[i])) {
                targetIndex = i;
            }
        }
        if (targetIndex == -1) {
            throw new RuntimeException("未知的参数名称: " + targetAttr);
        }
        return targetIndex;
    }

}
